from datetime import datetime
from decimal import Decimal

from money import Money

from merchant.gateways.base import Gateway, GatewayConfigError
from merchant.billing.credit_card import CreditCard
from merchant.utils import *

class BeanstreamGateway(Gateway):

    # URL for processing transactions.
    ENDPOINT_URL = 'https://www.beanstream.com/scripts/process_transaction.asp'
    
    # URL for creating / modifying Secure Payment Profiles.
    PROFILE_URL = "https://www.beanstream.com/scripts/payment_profile.asp"
    
    # Transactions supported by this gateway.
    TRANSACTIONS = {'purchase': 'P',}

    CVD_CODES = {
        '1': 'M',
        '2': 'N',
        '3': 'I',
        '4': 'S',
        '5': 'U',
        '6': 'P'
    }

    AVS_CODES = {
        '0': 'R',
        '5': 'I',
        '9': 'I'
    }
    
    MESSAGE_IDS = {
        '7':'Your card was declined. Please call your card\'s help desk as soon as possible, do not use your card until then.',
        '52':'Please call your card\'s help desk as soon as possible, do not use your card until then.',
        '1':'Your card was approved.',
    }
    
    CARD_TYPES = {
        'MC': 'mastercard',
        'VI': 'visa',
    }
    
    supported_countries = ['CA']
    supported_cardtypes = ['visa', 'master']
    default_currency = 'CAD'
    
    required = ['merchant_id', 'username', 'password']
        
    def purchase(self, amount=0.00, card=None, customer_code=None, **options):
        params = {}
        self.add_amount(params, amount)
        self.add_invoice(params, options)
        if card is not None:
            self.add_card(params, card)
        elif customer_code is not None:
            self.add_customer_code(params, customer_code)
        self.add_transaction_type(params, 'purchase')
        return self._commit(params)
        
    def add_customer_code(self, params, code):
        params['customerCode'] = code
        
    def add_amount(self, params, money):
        params['trnAmount'] = "%5.2f" % Money(money).amount
        
    def add_card(self, params, card):
        params["trnCardOwner"] = card.name
        params["trnCardNumber"] = card.number
        params["trnExpMonth"] = card.expiry_date.expiration().strftime("%m")
        params["trnExpYear"] = card.expiry_date.expiration().strftime("%y")
        params["trnCardCvd"] = card.verification_value
        
    def add_transaction_type(self, params, action):
        params['trnType'] = BeanstreamGateway.TRANSACTIONS[action]
        
    def add_invoice(self, params, options):
        params["trnOrderNumber"] = options["order_id"]
        params['ordName'] = options['order_name']
        params['ordEmailAddress'] = options['order_email']
        params['ordPhoneNumber'] = options['phone_number']
        params['ordAddress1'] = options['address']
        params['ordCity'] = options['city']
        params['ordProvince'] = options['province']
        params['ordPostalCode'] = options['postal_code']
        params['ordCountry'] = options['country']
        
    def _build_data(self, params):
        """
        Adds required data (merchant_id, etc) to the post data for this transaction.
        """
        params["requestType"] = "BACKEND"
        params["merchant_id"] = self.merchant_id
        params["username"] = self.username
        params["password"] = self.password
        params["vbvEnabled"] = 0
        params["scEnabled"] = 0
        return params
        
    def _build_response(self, raw_response):
        for k, v in raw_response.items():
            raw_response[k] = v[0]
        raw_response.update({'trnAmount': "%5.2f" % Money(raw_response.get("trnAmount")).amount })
        return Response(
            success=raw_response.get('trnApproved', 0) == '1',
            message=raw_response.get('messageText').replace('<LI>', '').replace('<br>', ''),
            raw_data=raw_response,
            authorization=raw_response.get('authCode', '0'),
            card_type=self.card_type_from_response(raw_response),
            transaction_id=raw_response.get('trnId', None),
            amount=Decimal(raw_response.get('trnAmount', '0').strip()),
            order_id=raw_response.get('trnOrderNumber', None)
        )
    
    # def authorization_from_response(self, data):
        # return u"%(trnId)s;%(trnAmount)s;%(trnType)s" % data
            
    def card_type_from_response(self, data):
        if data.get('paymentMethod', '') == "CC":
            try:
                return BeanstreamGateway.CARD_TYPES[data.get('cardType')]
            except KeyError:
                return None
                
    def _get_spp_data(self):            
        if not hasattr(self, 'spp_passcode'):
            raise GatewayConfigError('"spp_passcode" is required for using Secure Payment Profiles.')
        return {
            'serviceVersion': '1.0',
            'status': 'A',
            'merchantId': self.merchant_id,
            'passCode': self.spp_passcode,
        }
        
    def create_payment_profile(self, card, invoice):
        data = self._get_spp_data()
        data.update({'operationType': 'N',})
        self.add_card(data, card)
        self.add_invoice(data, invoice)
        response = XMLRequest(BeanstreamGateway.PROFILE_URL, data)
        setattr(response, 'success', response.responseCode == "1")
        setattr(response, 'card_type', BeanstreamGateway.CARD_TYPES.get(response.cardType, None))
        if self.gateway_mode == Gateway.TEST:
            print 'XMLResponse({'
            for k in sorted(response.__dict__.keys()):
                print '\t%s = %s' % (k, response.__dict__[k])
            print '})\n'
        return response
        
    def modify_payment_profile(self, customer_code, card, invoice):
        data = self._get_spp_data()
        data.update({'operationType': 'M',})
        data.update({'customerCode': customer_code,})
        self.add_card(data, card)
        self.add_invoice(data, invoice)
        # data.update(**params)
        response = XMLRequest(BeanstreamGateway.PROFILE_URL, data)
        setattr(response, 'success', response.responseCode == "1")
        setattr(response, 'card_type', BeanstreamGateway.CARD_TYPES.get(response.cardType, None))
        if self.gateway_mode == Gateway.TEST:
            print 'XMLResponse({'
            for k in sorted(response.__dict__.keys()):
                print '\t%s = %s' % (k, response.__dict__[k])
            print '})\n'
        return response
        
        

class TestCards(object):
    
    _approved = {
        'visa': "4030000010001234",
        'visa_100': '4504481742333',
        'mastercard': '5100000010001004',
    }
    
    _declined = {
        'visa': '4003050500040005',
        'mastercard': '5100000020002000',
    }
    
    invoice = dict(        
        order_id= datetime.now().microsecond,
        order_name="Bob Buyer",
        order_email="bob@buyer.com",
        phone_number='5555555555',
        address="1234 Main St",
        city="Victoria",
        province="BC",
        postal_code="V8T5J1",
        country="CA",
    )
    
    @classmethod
    def APPROVED(cls, ctype):
        return cls._card(cls._approved[ctype], ctype, 'APPROVED')
        
    @classmethod
    def DECLINED(cls, ctype):
        return cls._card(cls._declined[ctype], ctype, 'DECLINED')

    @classmethod    
    def _card(cls, number, ctype, mode):
        return CreditCard(
            number = number,
            verification_value="123",
            month = "10",
            year = "2010",
            card_type = ctype,
            name = 'Mr. %s %s' % (mode, ctype),
        )